#include "logdock.h"

#include <QtGui/qboxlayout.h>
#include <QtGui/qlabel.h>
#include <QtGui/qlistwidget.h>
#include <QtGui/qspinbox.h>
#include <QtGui/qtoolbutton.h>

QList<LogRecord> LogDock::m_records;
QMutex LogDock::m_msgMutex;

LogDock::LogDock(QWidget *parent) :
    QDockWidget(tr("Log"), parent)
{
    m_enableInfo = new QToolButton;
    m_enableInfo->setIcon(QIcon(":/images/info.png"));
    m_enableInfo->setToolTip(tr("Filter info"));
    m_enableInfo->setAutoRaise(true);
    m_enableInfo->setCheckable(true);
    m_enableInfo->setChecked(true);
    connect(m_enableInfo, SIGNAL(clicked(bool)), this, SLOT(filter()));
    m_enableWarning = new QToolButton;
    m_enableWarning->setToolTip(tr("Filter warnings"));
    m_enableWarning->setIcon(QIcon(":/images/warning.png"));
    m_enableWarning->setAutoRaise(true);
    m_enableWarning->setCheckable(true);
    m_enableWarning->setChecked(true);
    connect(m_enableWarning, SIGNAL(clicked(bool)), this, SLOT(filter()));
    m_enableError = new QToolButton;
    m_enableError->setToolTip(tr("Filter errors"));
    m_enableError->setIcon(QIcon(":/images/error.png"));
    m_enableError->setAutoRaise(true);
    m_enableError->setCheckable(true);
    m_enableError->setChecked(true);
    connect(m_enableError, SIGNAL(clicked(bool)), this, SLOT(filter()));
    QToolButton *clearLog = new QToolButton;
    clearLog->setToolTip(tr("Clear all logs"));
    clearLog->setIcon(QIcon(":/images/clear.png"));
    clearLog->setAutoRaise(true);
    connect(clearLog, SIGNAL(clicked()), this, SLOT(clearAll()));
    QVBoxLayout *buttonLayout = new QVBoxLayout;
    buttonLayout->addWidget(m_enableInfo);
    buttonLayout->addWidget(m_enableWarning);
    buttonLayout->addWidget(m_enableError);
    buttonLayout->addWidget(clearLog);
    buttonLayout->addStretch();

    m_logWindow = new QListWidget;
    QHBoxLayout *logLayout = new QHBoxLayout;
    logLayout->addLayout(buttonLayout);
    logLayout->addWidget(m_logWindow, 1);

    m_msgCountTo = new QSpinBox;
    m_msgCountTo->setRange(100, 100000);
    m_msgCountTo->setValue(2000);
    connect(m_msgCountTo, SIGNAL(valueChanged(int)), this, SLOT(filter()));

    m_msgCountFrom = new QSpinBox;
    m_msgCountFrom->setRange(50, m_msgCountTo->value() - 10);
    m_msgCountFrom->setValue(1000);
    connect(m_msgCountFrom, SIGNAL(valueChanged(int)), this, SLOT(filter()));

    QHBoxLayout *filterLayout = new QHBoxLayout;
    filterLayout->addWidget(new QLabel(tr("Max messages count from")));
    filterLayout->addWidget(m_msgCountFrom);
    filterLayout->addWidget(new QLabel(tr("to")));
    filterLayout->addWidget(m_msgCountTo);

    QHBoxLayout *bottomLayout = new QHBoxLayout;
    bottomLayout->addLayout(filterLayout);
    bottomLayout->addStretch(1);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(logLayout, 1);
    mainLayout->addLayout(bottomLayout);

    QWidget *w = new QWidget;
    w->setLayout(mainLayout);

    setWidget(w);
    startTimer(250);
}

LogDock::~LogDock()
{
}

void LogDock::addMessage(const QString &msg, QtMsgType msgType)
{
    m_msgMutex.lock();
    LogRecord rec;
    rec.msg = msg;
    rec.msgType = msgType;
    m_records.append(rec);
    m_msgMutex.unlock();
}

void LogDock::clearAll()
{
    while(m_logWindow->count())
        delete m_logWindow->takeItem(0);
}

void LogDock::filter()
{
    m_msgCountFrom->setMaximum(m_msgCountTo->value() - 10);
    const int lastMessage = m_logWindow->count() - 1;
    m_visibleCount = 0;
    for (int i = 0; i <= lastMessage; ++i)
    {
        QListWidgetItem *item = m_logWindow->item(lastMessage - i);
        if (m_visibleCount < m_msgCountFrom->value())
        {
            if (!filterItem(item))
                ++m_visibleCount;
        }
        else
            m_logWindow->setItemHidden(item, true);
    }
}

bool LogDock::filterItem(QListWidgetItem *item)
{
    LogItem *logItem = reinterpret_cast<LogItem *>(item);
    bool hidden = false;
    if ((logItem->messageType() == QtDebugMsg && !m_enableInfo->isChecked())
            || (logItem->messageType() == QtWarningMsg && !m_enableWarning->isChecked())
            || (logItem->messageType() >= QtCriticalMsg && !m_enableError->isChecked()))
    {
        hidden = true;
    }
    m_logWindow->setItemHidden(item, hidden);
    return hidden;
}

void LogDock::addLogItem(const LogRecord &rec)
{
    LogItem *item = new LogItem(rec.msg, static_cast<QtMsgType>(rec.msgType));
    m_logWindow->addItem(item);
    QColor c1;
    QColor c2;
    if (item->messageType() == QtWarningMsg
            || item->messageType() == QtCriticalMsg
            || item->messageType() == QtFatalMsg)
    {
        const int h = m_logWindow->visualItemRect(item).height();
        QLinearGradient grad(0, 0, 0, h);
        grad.setSpread(QGradient::ReflectSpread);
        if (item->messageType() == QtWarningMsg)
        {
            c1 = QColor(0xF8, 0xE7, 0x8D);
            c2 = QColor(0xEE, 0xC8, 0x28);
        }
        else
        {
            c1 = QColor(0xFD, 0xD7, 0xD2);
            c2 = QColor(0xF8, 0xB0, 0xA4);
        }
        grad.setColorAt(0, QColor(c1));
        grad.setColorAt(1, QColor(c2));
        item->setBackground(QBrush(grad));
    }
    bool filtered = filterItem(item);
    if (!filtered)
    {
        if (m_firstVisible < 0)
            m_firstVisible = m_logWindow->count() - 1;
        ++m_visibleCount;
    }
    if (m_visibleCount > m_msgCountTo->value())
    {
        do {
            if (m_logWindow->isItemHidden(m_logWindow->item(m_firstVisible)))
            {
                ++m_firstVisible;
                continue;
            }
            m_logWindow->setItemHidden(m_logWindow->item(m_firstVisible++), true);
            --m_visibleCount;
        } while (m_visibleCount > m_msgCountFrom->value() && m_firstVisible < m_logWindow->count());
    }
    if (!filtered)
        m_logWindow->scrollToBottom();
}

void LogDock::timerEvent(QTimerEvent *event)
{
    m_msgMutex.lock();
    while (!m_records.isEmpty())
        addLogItem(m_records.takeFirst());
    m_msgMutex.unlock();
}
